package com.neusoft.databus.agent.parser;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.lang3.StringUtils;
import org.snmp4j.smi.Counter64;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.SMIAddress;
import org.snmp4j.smi.UnsignedInteger32;
import org.snmp4j.smi.Variable;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.neusoft.databus.agent.bean.MetricConfig;
import com.neusoft.databus.common.bean.CommandBean;
import com.neusoft.databus.common.bean.MetricBean;
import com.neusoft.databus.common.constant.DatabusConstant;
import com.neusoft.databus.common.exception.ConfigErrorException;
import com.neusoft.databus.common.parser.Parser;
import com.neusoft.databus.common.utils.JexlMath;
import com.neusoft.databus.common.utils.JexlUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * SNMP解析器
 *
 * @author sunchf
 * @date 2019年1月28日 上午9:38:25
 */
@Slf4j
public class SnmpParser implements Parser {

    /**
     * 按指令分组,减少数据交互次数。<br />
     * 根据 {#MetricBean rule配置中的bulk字段，判断是否合并指标，并采用snmp bulk的方式采集数据}
     */
    @Override
    public Multimap<String, MetricBean> parseCollectRequest(final CommandBean commandBean) {

        if (SnmpParser.log.isDebugEnabled()) {
            SnmpParser.log.debug("parse snmp request metrics, {}, size is {}", SnmpParser.class.getSimpleName(),
                    commandBean.getResourceBean().stream().mapToInt(it -> it.getMetrics().size()).sum());
        }
        // 按指令分组,减少数据交互次数
        final Multimap<String, MetricBean> multimap = ArrayListMultimap.create();

        commandBean.getResourceBean().forEach(it -> {
            final Map<String, Object> config = it.getConfig();
            if (!config.containsKey("dn")) {
                throw new ConfigErrorException("请配置dn项");
            }
            final String dn = String.valueOf(config.get("dn"));

            for (final MetricBean metric : it.getMetrics()) {
                final String command = SnmpParser.formatOidStr(metric.getCommand()) + "." + dn;
                metric.setCommand(command);
            }
        });

        commandBean.getResourceBean().forEach(it -> {
            for (final MetricBean metric : it.getMetrics()) {
                String mergedCommand = SnmpParser.formatOidStr(metric.getCommand());
                if ((null != metric.getRule()) && metric.getRule().containsKey("bulk")) {
                    mergedCommand = mergedCommand.substring(0, mergedCommand.lastIndexOf("."));
                }
                multimap.put(mergedCommand, metric);
            }
        });

        if (SnmpParser.log.isDebugEnabled()) {
            SnmpParser.log.debug("parse snmp request metrics, {}, size after megred is {}",
                    SnmpParser.class.getSimpleName(), multimap.keySet().size());
        }

        return multimap;
    }

    @Override
    public void parseCollectResponse(final CommandBean commandBean) {

        commandBean.getResourceBean().forEach(it -> {
            final List<MetricBean> metrics = it.getMetrics();
            final JexlContext jexlContext = new MapContext();
            jexlContext.set("Math", JexlMath.getInstance());
            metrics.forEach(data -> {
                jexlContext.set(data.getVariable(), data.getOriginalValue());
            });
            metrics.stream().forEach(m -> {
                try {
                    final Variable variable = (Variable) m.getOriginalValue();
                    if (null == variable) {
                        SnmpParser.log.warn("{}:{}", m.getCommand(), "no return");
                        m.setOriginalValue("no return");
                    } else if (variable.isException() || (variable instanceof Null)) {
                        SnmpParser.log.warn("{}:{}", m.getCommand(), variable.toString());
                        m.setOriginalValue(variable.toString());
                    } else if ((variable instanceof Counter64) || (variable instanceof Integer32)
                            || (variable instanceof UnsignedInteger32)) {
                        m.setOriginalValue(variable.toLong());
                        m.setValue(variable.toLong());
                        jexlContext.set(m.getVariable(), m.getValue());
                    } else if ((variable instanceof OID) || (variable instanceof SMIAddress)) {
                        m.setOriginalValue(variable.toString());
                        m.setValue(variable.toString());
                        jexlContext.set(m.getVariable(), m.getValue());
                    } else if (variable instanceof OctetString) {
                        m.setOriginalValue(variable.toString());
                        m.setValue(this.octetStringtoString((OctetString) variable));
                        jexlContext.set(m.getVariable(), m.getValue());
                    }

                    switch (m.getValueType()) {
                    case BINARY:
                        final MetricConfig metricConfig = MetricConfig.create(m.getRule());
                        Integer bitStart = metricConfig.getBitStart();
                        if (null == bitStart) {
                            bitStart = 0;
                        }
                        Integer bitLength = metricConfig.getBitLength();
                        if (null == bitLength) {
                            bitLength = 1;
                        }
                        Integer length = metricConfig.getLength();
                        if (null == length) {
                            length = 1;
                        }

                        if (!StringUtils.isNumeric(String.valueOf(m.getValue()))) {
                            SnmpParser.log.warn("value is not numeric, cant't parse by bit");
                            return;
                        }
                        String binaryStr = Long.toBinaryString(Long.valueOf(String.valueOf(m.getValue())));
                        final int len = binaryStr.length();
                        if (binaryStr.length() < (length * 8)) {
                            final int fillLength = ((length * 8) - len);
                            for (int i = 0; i < fillLength; i++) {
                                binaryStr = "0" + binaryStr;
                            }
                        }
                        final String partial = binaryStr.substring(bitStart, bitStart + bitLength);
                        final Number number = Integer.valueOf(partial, 2);
                        m.setValue(number);
                        jexlContext.set(m.getVariable(), m.getValue());
                        break;
                    default:
                        break;
                    }
                } catch (final Exception e) {
                    SnmpParser.log.error(e.getMessage(), e);
                }
            });

            metrics.stream().filter(data -> data.isParseSuccess()).forEach(data -> {
                try {
                    final MetricConfig metricConfig = MetricConfig.create(data.getRule());
                    if (null != metricConfig.getExpression()) {
                        final Object value = JexlUtil.evaluate(metricConfig.getExpression(), jexlContext);
                        data.setValue(value);
                    }
                    final Map<String, Object> rule = data.getRule();
                    if ((null != rule) && rule.containsKey(DatabusConstant.METRIC_CONFIG_FLAG_PRECISION)) {
                        final BigDecimal decimal = new BigDecimal(data.getValue().toString());
                        final Object value = decimal.setScale(Integer.parseInt(
                                rule.get(DatabusConstant.METRIC_CONFIG_FLAG_PRECISION).toString()),
                                BigDecimal.ROUND_HALF_DOWN).doubleValue();
                        data.setValue(value);
                    }
                } catch (final Exception e) {
                    data.setValue(null);
                    SnmpParser.log.warn("解析指标时出错 >>>" + "metricCode: " + data.getCode() + " , originalValue: "
                            + data.getOriginalValue() + " , type: " + data.getOriginalValue().getClass());
                }
            });
        });

        commandBean.getResourceBean().forEach(it -> {
            final Map<String, Object> config = it.getConfig();
            final String dn = String.valueOf(config.get("dn"));

            for (final MetricBean metric : it.getMetrics()) {
                final String command = metric.getCommand().replace("." + dn, "");
                metric.setCommand(command);
            }
        });

    }

    public static String formatOidStr(final String oid) {
        String formatted = StringUtils.trim(oid);
        if (oid.startsWith(".")) {
            formatted = formatted.substring(1);
        }
        return formatted;
    }

    //  private boolean isPrintable(final byte[] value) {
    //     for (final byte aValue : value) {
    //         final char c = (char) aValue;

    //         if ((Character.isISOControl(c) ||
    //                 ((c & 0xFF) >= 0x80)) && (!Character.isWhitespace(c))) {
    //             return false;
    //         }
    //     }
    //     return true;
    // }

    private String octetStringtoString(final OctetString variable) {
        // if (this.isPrintable(variable.getValue())) {
            return new String(variable.getValue());
        // }
        // return variable.toHexString();
    }

}
